/**
 * http://auzll.iteye.com/
 */
package z.action;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import z.action.api.Api;
import z.action.namespace.Action;
import z.action.namespace.Namespace;
import z.tool.util.InputStreamLoader;
import z.tool.util.PropertiesLoader;
import z.tool.util.ZUtils;

import com.google.inject.internal.Maps;
import com.google.inject.internal.Sets;

/**
 * @author http://weibo.com/zhengll
 * @since 2011-3-10
 */
public class Dispatcher extends HttpServlet {
    private static final Log LOG = LogFactory.getLog(Dispatcher.class);

    private static final long serialVersionUID = 1l;

    private static final String DEFAULT_Z_ACTION_FILE = "zAction.properties";
    private static final String DEFAULT_Z_ACTION_EXTENSION = ".do";
    private static final Type DEFAULT_Z_ACTION_TYPE = Type.Api;
    
    private static final String Z_ACTION_PACKAGE_KEY = "z.action.package";
    private static final String Z_ACTION_EXTENSION_KEY = "z.action.extension";
    private static final String Z_ACTION_TYPE_KEY = "z.action.type";
    
    private String zActionFile = DEFAULT_Z_ACTION_FILE;
    private String zActionExtension = DEFAULT_Z_ACTION_EXTENSION;
    private Type zActionType = DEFAULT_Z_ACTION_TYPE;

    private Map<String, ClassMethodStruct> apiCache = Maps.newHashMap();
    private Map<String, Map<String, ClassMethodStruct>> namespaceCache = Maps.newHashMap();
    
    protected ServletConfig config;

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.zActionFile = ZUtils.getNotEmptyString(config.getInitParameter("zActionFile"), DEFAULT_Z_ACTION_FILE);
        init0();
    }

    private Collection<Class<?>> getActionClasses(String zPackage) {
        if ( null == zPackage ) {
            return Collections.emptySet();
        }
        
        Set<Class<?>> classes = Sets.newHashSet();
        String[] zPackageArray = zPackage.split(",");
        
        // 尝试直接加载class文件
        for (String pack : zPackageArray) {
            URL url = InputStreamLoader.getResource(pack.replaceAll("\\.", "/"));
            if (null == url) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("method:getActionClasses,pack:" + pack + ",desc:cannot get url");
                }
                continue;
            }
            
            File dir = null;
            try {
                dir = new File(url.toURI());
            } catch (URISyntaxException e1) {
            }
            
            if (null != dir && dir.isDirectory()) {
                String[] files = dir.list();
                for (String file : files) {
                    
                    // 忽略目录
                    if (new File(file).isDirectory()) {
                        continue;
                    }
                    
                    // 只要class文件
                    if (!file.endsWith(".class")) {
                        continue;
                    }
                    
                    String className = pack + '.' + file.substring(0, file.length()-6);
                    try {
                        classes.add(Class.forName(className));
                    } catch (Exception e) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("method:getActionClasses,pack:" + pack + ",className:" + className + ",desc:cannot found");
                        }
                    }
                }
            }
        }
        
        
        return classes;
    }
    
    private void init0() {
        Properties props = PropertiesLoader.loadProperties(zActionFile);
        if (null == props) {
            throw new ExceptionInInitializerError("cannot find zActionFile");
        }
        
        // 获取ActionPackage的路径
        String zPackage = ZUtils.trimStringNull(props.getProperty(Z_ACTION_PACKAGE_KEY));
        
        // 获取action后缀
        String zActionExtension = ZUtils.trimStringNull(props.getProperty(Z_ACTION_EXTENSION_KEY));
        if (null != zActionExtension) {
            this.zActionExtension = "." + zActionExtension;
        }
        
        // 判断类型
        String type = ZUtils.trimStringNull(props.getProperty(Z_ACTION_TYPE_KEY));
        if (null != type) {
            try {
                this.zActionType = Type.valueOf(type);
            } catch (Exception e) {
            }
        }

        if (null != zPackage) {
            Collection<Class<?>> actionClasses = getActionClasses(zPackage);
            for (Class<?> clazz: actionClasses) {
                //
                Namespace namespace = clazz.getAnnotation(Namespace.class);
                Map<String, ClassMethodStruct> namespaceActionMap = null;
                if (null != namespace) {
                    String namespaceValue = ZUtils.trimStringNull(namespace.value());
                    if ('/' == namespaceValue.charAt(0)) {
                        namespaceValue = namespaceValue.substring(1);
                    }
                    if (namespaceCache.containsKey(namespaceValue)) {
                        throw new IllegalArgumentException("namespace - " + namespaceValue + " already exist ");
                    }
                    namespaceActionMap = Maps.newHashMap();
                    namespaceCache.put(namespaceValue, namespaceActionMap);
                }
                
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method: methods) {
                    Api api = method.getAnnotation(Api.class);
                    if (null != api) {
                        String apiValue = ZUtils.trimStringNull(api.value());
                        if (null == apiValue) {
                            throw new IllegalArgumentException("apiValue for api[" + api + "] is null ");
                        }
                        if (apiCache.containsKey(apiValue)) {
                            throw new IllegalArgumentException("api - " + apiValue + " already exist ");
                        }
                        
                        apiCache.put(apiValue, new ClassMethodStruct(clazz, method));
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("method:init0,desc:cache success,class:" + clazz.getName()
                                    + ",method:" + method.getName() + ",api:"  + apiValue);
                        }
                    }
                    
                    if (null != namespaceActionMap) {
                        Action action = method.getAnnotation(Action.class);
                        if (null != action) {
                            String actionValue = ZUtils.trimStringNull(action.value());
                            if (null == actionValue) {
                                throw new IllegalArgumentException("actionValue for action[" + action + "] is null ");
                            }
                            if (namespaceActionMap.containsKey(actionValue)) {
                                throw new IllegalArgumentException("action - " + actionValue + " already exist ");
                            }
                            
                            namespaceActionMap.put(actionValue, new ClassMethodStruct(clazz, method));
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("method:init0,desc:cache success,class:" + clazz.getName()
                                        + ",method:" + method.getName() + ",namespace:" + namespace.value() + ",action:"  + actionValue);
                            }
                        }
                    }
                }
                // 
            }
        }

    }
    
    private ClassMethodStruct getClassMethodStruct(Context actionContext, HttpServletRequest request, Type type) throws ServletException {
        // 调用的api名称
        String api = null;
        
        // 调用的namespace或action
        String namespace = null;
        String action = null;
        
        // 本次请求所对应的ClassMethodStruct
        ClassMethodStruct classMethoStruct = null;
        
        if (Type.Api == type) {
            api = actionContext.tryToGetApi();
            if (!ZUtils.isEmpty(api)) {
                classMethoStruct = apiCache.get(api);
            }
            
        } else if (Type.Namespace == type) {
            String uri = request.getRequestURI().substring(request.getContextPath().length());
            if ('/' == uri.charAt(0)) {
                uri = uri.substring(1);
            }
            String[] namespaceAndAction = uri.split("/", 2);
            if (2 == namespaceAndAction.length) {
                namespace = namespaceAndAction[0];
                action = namespaceAndAction[1];
            } else {
                namespace = "";
                action = namespaceAndAction[0];
            }
            
            if (action.endsWith(zActionExtension)) {
                action = action.substring(0, action.length() - zActionExtension.length());
            }
            
            Map<String, ClassMethodStruct> actionClassMethodStruct = namespaceCache.get(namespace);
            if (null != actionClassMethodStruct) {
                classMethoStruct = actionClassMethodStruct.get(action);
            }
            
        }
        
        if (LOG.isDebugEnabled()) {
            LOG.debug("method:getClassMethodStruct,api:" + api + ",namespace:" + namespace + ",action:" + action + ",classMethoStruct:" + classMethoStruct);
        }
        
        return classMethoStruct;
    }

    protected void dispatcher(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        try {
            // 把HttpServletRequest,
            // HttpServletResponse放入ThreadLocal中；后面会在finally中删除
            Context actionContext = Context.setActionContext(request, response);
            
            // 解释本次请求所对应的ClassMethodStruct
            ClassMethodStruct classMethoStruct = getClassMethodStruct(actionContext, request, zActionType);
            
            // 如果按照指定的zActionType未能获取到classMethoStruct，那么尝试所有的Type
            if (null == classMethoStruct) {
                for (Type t : Type.values()) {
                    if (t == zActionType) {
                        // 排除已检查过的
                        continue;
                    }
                    classMethoStruct = getClassMethodStruct(actionContext, request, t);
                    if (null != classMethoStruct) {
                        break;
                    }
                }
            }
            
            if (null != classMethoStruct) {
                Class<?> actionClass = classMethoStruct.getClazz();
                Method method = classMethoStruct.getMethod();
                try {
                    Class<?>[] paramTypes = method.getParameterTypes();
                    Object resultObj = null;
                    if (0 == paramTypes.length) {
                        resultObj = method.invoke(getInstance(actionClass));
                    } else {
                        Object[] args = new Object[paramTypes.length];
                        for (int i = 0; i < paramTypes.length; i++) {
                            Class<?> PType = paramTypes[i];
                            if (Context.class == PType) {
                                args[i] = actionContext;
                            } else {
                                args[i] = null;
                            }
                        }
                        resultObj = method.invoke(getInstance(actionClass), args);
                    }

                    if (resultObj instanceof Result) {
                        // type: Result

                        Result actionResult = (Result) resultObj;
                        if (actionResult.isRedirect()) {
                            // redirect
                            String view = actionResult.getView();

                            if (actionResult.getValues().size() > 0) {
                                StringBuilder newView = new StringBuilder(view);
                                int qIndex = actionResult.getView().indexOf('?');
                                if (qIndex == -1) {
                                    newView.append('?');
                                }
                                for (String key: actionResult.getValues().keySet()) {
                                    Object value = actionResult.getValues().get(key);
                                    newView.append(key).append('=').append(value).append('&');
                                }
                                if ('&' == newView.charAt(newView.length() - 1)) {
                                    newView.deleteCharAt(newView.length() - 1);
                                }
                                view = newView.toString();
                            }
                            response.sendRedirect(view);

                        } else {
                            // forword
                            for (String key: actionResult.getValues().keySet()) {
                                Object value = actionResult.getValues().get(key);
                                request.setAttribute(key, value);
                            }
                            request.getRequestDispatcher(actionResult.getView()).forward(request, response);
                        }

                    } else if (resultObj instanceof String) {
                        // type: String
                        request.getRequestDispatcher((String) resultObj).forward(request, response);

                    } else {
                        // type: unknow, do nothing
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("method:dispatcher,desc:unknow result type");
                        }
                        throw new ServletException("unknow result type");
                    }

                } catch (Exception e) {
                    LOG.error("method:dispatcher,desc:execute error", e);
                    throw new ServletException(e.getMessage());
                }
            } else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("method:dispatcher,desc:cannot find classMethoStruct");
                }
                throw new ServletException("cannot find classMethoStruct");
            }

        } finally {
            // 从ThreadLocal中删除HttpServletRequest, HttpServletResponse
            Context.removeActionContext();
        }
    }

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        dispatcher(request, response);
    }
    
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        dispatcher(request, response);
    }

    protected Object getInstance(Class<?> action) {
        // 子类可以覆盖这个方法，从spring和guice等容器里面获取instance
        try {
            return action.newInstance();
        } catch (Exception e) {
            throw new ActionError(e);
        }
    }
}
